package CalendarWS;

import com.google.gdata.client.GoogleAuthTokenFactory.UserToken;
import com.google.gdata.client.calendar.*;
import com.google.gdata.data.*;
import com.google.gdata.data.calendar.*;
import com.google.gdata.data.extensions.*;
import com.google.gdata.util.*;
import com.google.gdata.client.authn.oauth.*;

import java.net.*;
import java.io.*;
import java.sql.*;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.*;
import javax.servlet.http.*;

import sample.util.*;

/**
 * @author mike wang
 * Calendar manager facilitates communication between Google Calendar and SMO. 
 * Functions exists to make, update, get, and delete events from a Google cal.
 * Does not provide database connection.
 */
public class CalUpdater {
	// when authenticated, this will be interface to web
    private CalendarService cs;
    private URL urlDefaultPrivateFull;
    
	/**
	 * Constructor
	 */
	public CalUpdater() {
		cs = new CalendarService("Calendar Module in SMO");
        try {
        	// URL to post events: private=visibility, full=projection
			urlDefaultPrivateFull = new URL("https://www.google.com/calendar/feeds/default/private/full");
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
	} // end constructor
	
	/**
     * Setup Authentication for interaction with Google.
     * @param userName - Valid userName
     * @param password - Valid password
     */
    public void authenSetup(String userName, String password) {

    	// TODO: use OAuth
    	try { // tries to est. authentication
    		cs.setUserCredentials(userName, password); 
    	} catch(AuthenticationException e){
    		System.out.println("authen error");
    	}
    	
    	UserToken authenToken = (UserToken) cs.getAuthTokenFactory().getAuthToken();
    	System.out.println(authenToken.getValue().toString());

    	try{ // writing out authentication token
	    	FileWriter fstream = new FileWriter("authenFile.txt");
	    	BufferedWriter out = new BufferedWriter(fstream);
	    	out.write(authenToken.getValue().toString());
	    	out.close();
    	} catch(Exception e) {
    		  System.out.println("error saving file ... " + e.getMessage());
    	}
    } // end method
    
    /**
     * Loads stored token for authentication.
     * @throws OAuthException 
     */
    public void authenTokenLoad() throws OAuthException {
    	GoogleOAuthParameters oauthParameters = new GoogleOAuthParameters();
    	  oauthParameters.setOAuthConsumerKey("754652786615.apps.googleusercontent.com");
    	  oauthParameters.setOAuthConsumerSecret("KIW3dS5X1O9cnWvFYocmRsUH");
    	  oauthParameters.setOAuthToken("1/LCG5xbq5yN0RXl-uBS5rB1UGMZc7KzBItYZv7B3FpRY");
    	  oauthParameters.setOAuthTokenSecret("t36FnwSq1Bdt-VIWtUG9oBZc");
    	  
    	  cs.setOAuthCredentials(oauthParameters, new OAuthHmacSha1Signer());

		// TODO: use OAuth
//		String authenTokenRead = "";
//		try { // reading token from file
//				FileReader fstream = new FileReader("authenFile.txt");
//				BufferedReader read = new BufferedReader(fstream);
//				authenTokenRead = read.readLine();
//		} catch(Exception e){
//				System.out.println("error while reading file" + e.getMessage());
//		}
//		cs.setUserToken(authenTokenRead); // authenticates calendar-service obj
    } // end method


	/**
	 * Adds one, non-recurrent, event to Google Calendars for given start and end times.
	 * @param url - URL feed for posting new event
	 * @param title - Title of event object
	 * @param content - Content of event object
	 * @param startTime - Time of event start
	 * @param endTime - Time of event end
	 * @return Null if event unsuccessfully added, else returns the CalendarEventEntry object.
	 */
	public CalendarEventEntry addEvent(	String title, 
			String content,
			DateTime startTime,
			DateTime endTime) {
		CalendarEventEntry newEntry = new CalendarEventEntry();
		newEntry.setTitle(new PlainTextConstruct(title));
		newEntry.setContent(new PlainTextConstruct(content));
		
		When eventTimes = new When();
		eventTimes.setStartTime(startTime);
		eventTimes.setEndTime(endTime);
		newEntry.addTime(eventTimes);
		
		// event-transparency that is opaque will show time as BUSY
		newEntry.setTransparency(BaseEventEntry.Transparency.OPAQUE);
		
		CalendarEventEntry insertedEntry = null;
		try {
			insertedEntry = cs.insert(urlDefaultPrivateFull, newEntry);
		} catch (IOException e) {
			System.out.println("IO exeption within addEvent");
			e.printStackTrace();
		} catch (ServiceException e) {
			System.out.println("Service exception within addEvent");
			e.printStackTrace();
		}
		return insertedEntry;
	}
	/**
	 * Adds one, non-recurrent, event to Google Calendars given start time with
	 *   given duration in minutes and hours. 
	 * @param url - URL feed for posting new event
	 * @param title - Title of event object
	 * @param content - Content of event object
	 * @param startTime - Time of event start
	 * @param durationHours - Duration in hours
	 * @param durationMinutes - Duration in minutes
	 * @return Null if event unsuccessfully added, else returns the CalendarEventEntry object.
	 */
	public CalendarEventEntry addEvent(	String title, 
			String content,
			DateTime startTime,
			int durationHours,
			int durationMinutes) {
		CalendarEventEntry newEntry = new CalendarEventEntry();
		newEntry.setTitle(new PlainTextConstruct(title));
		newEntry.setContent(new PlainTextConstruct(content));
		
		When eventTimes = new When();
		eventTimes.setStartTime(startTime);
		DateTime endTime = new DateTime(startTime.getValue() + 1000*60*(durationHours*60 + durationMinutes));
		endTime.setTzShift(startTime.getTzShift());
		eventTimes.setEndTime(endTime);
		newEntry.addTime(eventTimes);
		
		// event-transparency that is opaque will show time as BUSY
		newEntry.setTransparency(BaseEventEntry.Transparency.OPAQUE);
		
		CalendarEventEntry insertedEntry = null;
		try {
			insertedEntry = cs.insert(urlDefaultPrivateFull, newEntry);
		} catch (IOException e) {
			System.out.println("IO exeption within addEvent");
			e.printStackTrace();
		} catch (ServiceException e) {
			System.out.println("Service exception within addEvent");
			e.printStackTrace();
		}
		return insertedEntry;
	} // end method
	
	/**
	 * Check if given start and end time conflict with existing start and end times.
	 * @param startTime - Start of time range to query for availability
	 * @param endTime - End of time range to query for availability
	 * @return True if no conflicting events found for given time range, else false.
	 */
	public Boolean availableToSchedule(	DateTime startTime,	
			DateTime endTime){
		// padding around query time to catch more conflicts
		long padding = 1000*60*5; // 5 min. 
		startTime.setValue(startTime.getValue() + padding);
		endTime.setValue(endTime.getValue() + padding);
		// query for selected time range
		CalendarQuery q = new CalendarQuery(urlDefaultPrivateFull);
		q.setMaxResults(99); // default 25 results returned 
		q.setMinimumStartTime(startTime); 
		q.setMaximumStartTime(endTime); 
		CalendarEventFeed resultFeed = null;
		try {
			resultFeed = cs.query(q, CalendarEventFeed.class);
		} catch (IOException e) {
			System.out.println("IO exception in availableToSchedule method.");
			e.printStackTrace();
		} catch (ServiceException e) {
			System.out.println("Service exception in availableToSchedule method.");
			e.printStackTrace();
		}
		// iterate through found events and look for conflicts
		for (int i = 0; i < resultFeed.getEntries().size(); ++i) {
			CalendarEventEntry entry = resultFeed.getEntries().get(i); // get 1 event entry
			DateTime existingStartTime = entry.getTimes().get(0).getStartTime(); 
			DateTime existingEndTime = entry.getTimes().get(0).getEndTime();
			// left.compareTo(right): 
			// 		-1 iff left  < right
			//  	 0 iff left == right
			//  	 1 iff left  > right
			
			System.out.println("\t Title: " + entry.getTitle().getPlainText());
			System.out.println("\t Start from: " + entry.getTimes().get(0).getStartTime().toString());
			System.out.println("\t Ends At: " + entry.getTimes().get(0).getEndTime().toString());
			
			// start times are equal
			if(existingStartTime.compareTo(startTime) == 0 &&
					entry.getTransparency().getValue().compareTo(BaseEventEntry.Transparency.OPAQUE_VALUE) == 0)
				return false;
			// new event starts in the middle of existing event
			if(existingStartTime.compareTo(startTime) < 0 &&
					existingEndTime.compareTo(startTime) > 0 &&
					entry.getTransparency().getValue().compareTo(BaseEventEntry.Transparency.OPAQUE_VALUE) == 0)
				return false;
			// new event ends in the middle of existing event (ok to end exactly at start of existing)
			if(endTime.compareTo(existingStartTime) > 0 &&
					endTime.compareTo(existingEndTime) < 0 &&
					entry.getTransparency().getValue().compareTo(BaseEventEntry.Transparency.OPAQUE_VALUE) == 0)
				return false;
			// new event starts before event but ends after event ends 
			if(startTime.compareTo(existingStartTime) < 0 &&
					endTime.compareTo(existingEndTime) > 0 &&
					entry.getTransparency().getValue().compareTo(BaseEventEntry.Transparency.OPAQUE_VALUE) == 0)
				return false;
		}
		return true; // if no conflict found
	} // end method
	
	/**
	 * Retrieves events from Google and compares each to locally registered events. 
	 * If retrieved events do not exist in database, create them.
	 * If retrieved events are special (ie: hours worked), update db
	 * @return True if successful
	 */
	public Boolean syncEventsFromGoogle(){
		// btaskinfo is table to get/set events
		return true;
	} // end method
	
	/**
	 * @return True if event updated successfully
	 */
	public Boolean updateEvent() {
		return true;
	} // end method
	
	/**
	 * Gets the events from a specified time range
	 * @return retrieved event
	 */
	public CalendarEventEntry getEvent() {
		
		return null;
	}
	
} // end class
